package it.trekker.prefetch.ejb;

import java.util.List;

import javax.annotation.Resource;
import javax.annotation.security.RolesAllowed;
import javax.ejb.*;
import org.apache.log4j.Logger;
import org.jboss.security.annotation.SecurityDomain;

import it.trekker.dao.Excursion;
import it.trekker.dao.GPSCoordinate;
import it.trekker.dao.IExcursionDAO;
import it.trekker.dao.User;
import it.trekker.prefetch.ExcursionsCache;
import it.trekker.prefetch.ICacheLoader;
import it.trekker.prefetch.IExcursionDAOWithPrefetch;


/**
 * @author stefano
 *
 * This class implements a DAO pattern to give access to excursions data in database,
 * with data prefetching.
 * The cache of this object contains data for excursions included within an area 
 * with radius = SEARCHING_RADIUS (constant), centered in the point where the excursion 
 * searched starts. When a request of close excursions happened, excursions included 
 * within an area with radius = SEARCHING_RADIUS/2 from the searched one are returned.
 * 
 */

//@Interceptors({ExcursionDAOWithPrefetchLogger.class})
@SecurityDomain("TrekkerRealm")
@RolesAllowed("viewer")
@Stateful
@Local(IExcursionDAOWithPrefetch.class)
public class ExcursionDAOWithPrefetch implements IExcursionDAOWithPrefetch {

	private static final Logger logger = Logger.getLogger(ExcursionDAOWithPrefetch.class.getCanonicalName());

	private double searchingRadius = 100;
	private boolean cacheEnabled;

	private GPSCoordinate centerOfTheSearch;

	@Resource
	SessionContext ctx;

//	@EJB(lookup="ejb:TrekkerServer/TrekkerServer.jar/EJB3ExcursionDAO!it.trekker.dao.IExcursionDAO")
	@EJB
	IExcursionDAO excursionDAO;
//	@EJB(lookup="ejb:TrekkerServer/TrekkerServer.jar/CacheLoader!it.trekker.prefetch.ICacheLoader")
	@EJB
	ICacheLoader cacheLoader;

	private ExcursionsCache cache;

	
	
	

	public ExcursionDAOWithPrefetch() {
		cache = new ExcursionsCache();
		cacheEnabled = true;
	}


	
	public double getSearchingRadius() {
		return searchingRadius;
	}



	public void setSearchingRadius(double searchingRadius) {
		this.searchingRadius = searchingRadius;
	}



	public boolean isCacheEnabled() {
		return cacheEnabled;
	}



	public void setCacheEnabled(boolean cacheEnabled) {
		if(this.cacheEnabled && !cacheEnabled)
			cache.invalidateCache();
		
		this.cacheEnabled = cacheEnabled;
	}



	public ExcursionsCache getCache() {
		return cache;
	}



	public int insert(Excursion excursion) {
		return excursionDAO.insert(excursion);
	}

	

	public int remove(int id) {
		return excursionDAO.remove(id);
	}

	

	public int update(Excursion excursion) {
		return excursionDAO.update(excursion);
	}

	
	
	public Excursion findExcursionById(int id, boolean loadCoordinates) {
		
		/*** Cache disabled ***/
		
		if(!cacheEnabled)
			return excursionDAO.findExcursionById(id, loadCoordinates);
		
		
		/*** Cache enabled ***/
		
		// Retrieve from cache
		Excursion excursion = cache.getExcursionById(id);
		
		if(excursion == null) {
			logger.info("excursion " + id + " not in cache: retrieving it from database");

			// Retrieve from database
			excursion = excursionDAO.findExcursionById(id, loadCoordinates);
			centerOfTheSearch = excursion.getStartPoint();

			cache.invalidateCache();
			
			/* 
			 * In this way, client awaits until loadCache() method ends.
			 * Is this a JBoss 7.1.1 bug?
			 */
//			ctx.getBusinessObject(IExcursionDAOWithPrefetch.class).loadCache(excursion);

			/*
			 * With an external stateless session bean annotated with @Asynchronous,
			 * loadCache() works asynchronously for an external client.
			 */
			cacheLoader.loadCacheWithCloseExcursions(cache, excursion, searchingRadius/2);
		}
		else {
			logger.info("excursion " + id + " in cache");
			
			if(GPSCoordinate.distanceBetween(centerOfTheSearch, excursion.getStartPoint()) > searchingRadius/2) {
				cache.invalidateCache();
				cacheLoader.loadCacheWithCloseExcursions(cache, excursion, searchingRadius/2);
			}
		}

		return excursion;
	}


	
	public List<Excursion> findExcursionByName(String name) {
		return excursionDAO.findExcursionByName(name);
	}

	

	public List<Excursion> findAllExcursions() {
		return excursionDAO.findAllExcursions();
	}

	

	public List<Excursion> findCloseExcursions(Excursion excursion) {
		List<Excursion> result;
		
		/*** Cache disabled ***/
		
		if(!cacheEnabled)
			return excursionDAO.findCloseExcursions(excursion, searchingRadius);
		
		
		/*** Cache enabled ***/
		
		if(cache.getSize() > 0) {
			// Retrieve from cache
			result = cache.getExcursionsFromCache(excursion, searchingRadius/2);
		}
		else {
			// Retrieve from database and load cache
			result = excursionDAO.findCloseExcursions(excursion, searchingRadius);
			cacheLoader.loadCacheWithGivenExcursions(cache, result);
		}
		return result;
	}

	

	public List<Excursion> findCloseExcursions(GPSCoordinate gpsCoord) {
		
		/*** Cache disabled ***/
		
		if(!cacheEnabled)
			return excursionDAO.findCloseExcursions(gpsCoord, searchingRadius);


		/*** Cache enabled ***/
		
		// Retrieve from database
		List<Excursion> list = excursionDAO.findCloseExcursions(gpsCoord, searchingRadius/2);

		// Add to cache asynchronously
		cacheLoader.loadCacheWithGivenExcursions(cache, list);

		return list;
	}

	
	public List<Excursion> findExcursionsByUser(User user) {
		List<Excursion> excursions = excursionDAO.findExcursionsByUser(user);

		return excursions;
	}
	


	/*
	 * This method doesn't works asynchronously for external client because 
	 * this session bean is stateful: client awaits until loadCache() method ends.
	 * Is this a JBoss 7.1.1 bug?
	 */
//	@Asynchronous
//	public void loadCache(Excursion excursion) {
//
//		logger.info("start loadCache");
//
//		if(excursion != null) {
//			cache.invalidateCache();
//			cache.addExcursion(excursion);
//			List<Excursion> closeExcursions = excursionDAO.findCloseExcursions(excursion, searchingRadius);
//			if(closeExcursions != null) {
//				for(Excursion exc : closeExcursions) {
//					cache.addExcursion(exc);
//				}
//			}
//		}
//
//		try {
//			Thread.sleep(4000);
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}
//
//		logger.info("end loadCache");
//	}
}
